﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Diagnostics;

namespace InterpolatorDotNET
{
    public class GenericToDictionary
    {
        public static Dictionary<string, object> Flatten(params object[] classObject)
        {
            Dictionary<string, object>[] dictionaries = new Dictionary<string, object>[classObject.Length];
            for (int i = 0; i < classObject.Length; i++)
                dictionaries[i] = __toDictionary(classObject[i]);

            return DictionaryMerge(dictionaries.ToArray());
        }
        private static Dictionary<string, object> __toDictionary(object classObject)
         {
             return __toDictionary(classObject, "");
         }
        private static Dictionary<string, object> __toDictionary(object classObject, string classprefix)
        {
            if (classObject == null) return null;

            Dictionary<string, object> flat = new Dictionary<string, object>();

            // handle interfaces or baseclasses...
            if (classObject.GetType().IsInterface || classObject.GetType().IsAbstract || classObject.GetType().Name == "Object[]")
            {
                var q = from t in Assembly.GetExecutingAssembly().GetTypes()
                        where t.GetInterfaces().Contains(classObject.GetType()) && t.IsInstanceOfType(classObject)
                        select t;
                Type tt = q.FirstOrDefault();
                if (tt != null)
                    classObject = Convert.ChangeType(classObject, tt);
            }

            string classname = classObject.GetType().Name;
            if (!string.IsNullOrEmpty(classprefix))
                classname = classprefix;

            foreach (PropertyInfo prop in classObject.GetType().GetProperties())
            {
                try
                {
                    object val = prop.GetValue(classObject, null);

                    if (val == null) continue;

                    if (InterpolaterTools.IsPrimative(val))
                    {
                        flat.Add(string.Format("{0}.{1}", classname, prop.Name).ToLower(), prop.GetValue(classObject, null));
                    }
                    else if (InterpolaterTools.IsEnumerable(val))
                    {
                        int counter = -1;
                        PropertyInfo countprop = val.GetType().GetProperty("Count");
                        if (countprop != null)
                            counter = (int)countprop.GetValue(val, null);
                        if (counter == -1)
                        {
                            PropertyInfo lengthprop = val.GetType().GetProperty("Length");
                            if (lengthprop != null)
                                counter = (int)lengthprop.GetValue(val, null);
                        }
                        flat.Add(string.Format("{0}.{1}", classname, prop.Name).ToLower(), string.Format("[{0}[{1}]]", val.GetType().FullName, counter));
                    }
                    else if (val.GetType().IsEnum)
                    {
                        string e = Enum.GetName(val.GetType(), val);
                        flat.Add(string.Format("{0}.{1}", classname, prop.Name).ToLower(), e.ToUpper());
                    }
                    else if (val.GetType() == typeof(System.Drawing.Color))
                    {
                        flat.Add(string.Format("{0}.{1}", classname, prop.Name).ToLower(), val.ToString());
                    }
                    else
                    {
                        Interpolate interAttr = val.GetType().GetCustomAttributes(typeof(Interpolate), false)
                            .FirstOrDefault() as Interpolate;
                        if (interAttr == null || (interAttr.CanBeFlattened == false))
                            continue;

                        Dictionary<string, object> mergeDictionary = __toDictionary(val, prop.Name);
                        foreach (KeyValuePair<string, object> kv in mergeDictionary)
                            flat.Add(string.Format("{0}.{1}", classname, kv.Key).ToLower(), kv.Value);
                    }
                }
                catch (Exception ex)
                {
                    Debug.WriteLine(string.Format("Exception occured {0}::{1}\n{2}",classname, prop.Name, ex.Message));
                }
            }

            return flat;
        }
        internal static Dictionary<string, object> DictionaryMerge(Dictionary<string, object>[] dictionaries)
        {
            var result = dictionaries.SelectMany(dict => dict)
                         .ToLookup(pair => pair.Key, pair => pair.Value)
                         .ToDictionary(group => group.Key, group => group.First());
            return result;
        }
    }

    public class Interpolator
    {
        public enum INTERPLATE_OPTIONS { SHOW_NO_MERCY ,NO_ERROR_MISSINGMATCH, ARRAY_NUMBER_ONLY}
        /// <summary>
        /// Finds and replaces values within the {classname.property} nomiclature 
        /// </summary>
        /// <param name="str"></param>
        /// <param name="values"></param>
        /// <returns></returns>
        public static string Interpolate(String str, params object[] values)
        {
            Dictionary<string, object> dictionaries = new Dictionary<string, object>();
            
            INTERPLATE_OPTIONS options = INTERPLATE_OPTIONS.SHOW_NO_MERCY;
            List<object> toflatten = new List<object>();
            for(int x=0; x<values.Length; x++)
            {
                if (values[x].GetType() == typeof(INTERPLATE_OPTIONS))
                {
                    INTERPLATE_OPTIONS? oo = values[x] as INTERPLATE_OPTIONS?;
                    if (oo != null)
                        options = options | (INTERPLATE_OPTIONS)values[x];
                }
                else if (values[x].GetType() == typeof(Dictionary<string, object>))
                {
                    dictionaries = GenericToDictionary.DictionaryMerge(new Dictionary<string, object>[] { dictionaries, (Dictionary<string, object>) values[x] });
                }
                else
                    toflatten.Add(values[x]);
            }

            Dictionary<string, object> tmp = GenericToDictionary.Flatten(toflatten.ToArray());
            dictionaries = GenericToDictionary.DictionaryMerge(new Dictionary<string, object>[] { dictionaries, tmp });
            

            System.Text.RegularExpressions.Regex eval =
                new System.Text.RegularExpressions.Regex("{.+?}", System.Text.RegularExpressions.RegexOptions.Compiled);

            int iReplaced = 0;
            System.Text.RegularExpressions.MatchCollection mc = eval.Matches(str);

            foreach (System.Text.RegularExpressions.Match m in mc)
            {
                string matchkey = m.Value.Trim(new char[]{'{','}'}).ToLower();
                if (dictionaries.ContainsKey(matchkey))
                {
                    str = str.Replace(m.Value, dictionaries[matchkey].ToString());
                    iReplaced++;
                }
            }

            if (iReplaced != mc.Count && ((options & INTERPLATE_OPTIONS.NO_ERROR_MISSINGMATCH) != INTERPLATE_OPTIONS.NO_ERROR_MISSINGMATCH))
            {
                string ERROR = "One or more matches cound not be found in the classname.property collection";
                Debug.WriteLine(ERROR);
                throw new MissingFieldException(ERROR);
            }

            return str;
        }
       
    }

    [AttributeUsage(AttributeTargets.Class)]
    public class Interpolate : Attribute
    {
        public bool CanBeFlattened = true;
        public Interpolate() { }
        public Interpolate(bool CanBeFlattened) 
        {
            this.CanBeFlattened = CanBeFlattened;
        }
    }
}
